package com.gdcnano.nanoamp.media.sound
{
    import com.gdcnano.nanoamp.media.sound.playlist.ITrack;
    
    import flash.errors.IllegalOperationError;
    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.events.ProgressEvent;
    import flash.events.TimerEvent;
    import flash.media.Sound;
    import flash.media.SoundChannel;
    import flash.media.SoundTransform;
    import flash.net.URLRequest;
    import flash.utils.Timer;
    
    import mx.utils.ObjectUtil;
    import mx.utils.StringUtil;
    
    
    /**
     * Sound channel manager class
     * @author gareevm
     *
     */
    public class SoundManager extends EventDispatcher
    {
    	/*
    		EVENT CONSTANTS
    	*/
    	public static const TRACK_POSITION_CHANGED:String = "trackPositionChangedEvent";
    	
    	public static const TRACK_LOADING_PROGRESS_CHANGED:String = "trackLoadingProgressChagnedEvent";
		
		public static const TRACK_PLAYING_COMPLETE:String = "trackPlayingComplete";
		
		private static const BUBBLE_EVENT:Boolean = true;
		
		private static const TIME_DELIMITER:String = ":";
		
		private static const DEFAULT_TIME:String = "0:00";
		
		private static const DEFAULT_VOLUME_LEVEL:Number = 1;
        
		/*---------------------------------
			States
		-----------------------------------*/
		
		private var _playState:ISoundState;
		
		private var _stopState:ISoundState;
		
		private var _pauseState:ISoundState;
		
		private var _currentState:ISoundState;
		
		
		public function get playState():ISoundState
		{
			return _playState;
		}
		
		public function get stopState():ISoundState
		{
			return _stopState;
		}
		
		public function get pauseState():ISoundState
		{
			return _pauseState;
		}
		
		/* ----------------------------------------- */
		
		private static var _instance:SoundManager;
		
		/**
         * Track loading progress percentage value [0-100]
         */
        private var _loadingProgressValue:uint;
		
		/**
		 * Defines current track position in percentage value [0-100]
		 */
		private var _trackDisplayPosition:Number;
		
		private var _trackPosition:Number = 0;
		
		private var _sound:Sound;
		
		private var _soundChannel:SoundChannel;
		
		private var _volume:Number = DEFAULT_VOLUME_LEVEL;
		
		private var _mute:Boolean = false;
		
		[Bindable]
		public var trackTime:String = DEFAULT_TIME;
		
		[Bindable]
		public var currentTime:String = DEFAULT_TIME;
		
		[Bindable]
		public var id3Info:ID3InfoWrapper;
		
		/**
		 * Defines time intervals to renew
		 * @see trackPosition
		 */
		private var _trackPositionTimer:Timer;
		
		/**
		 * Gets or sets the volume.
		 * 
		 */
		public function set volume(newVolume:Number):void
		{
			if(newVolume > 1)
			{
				_volume = 1;
			}
			else if(newVolume < 0)
			{
				_volume = 0;
			} else 
			{
				_volume = newVolume;
			}
			if(!_mute)
			{
				if(_soundChannel != null)
				{
					var st:SoundTransform = _soundChannel.soundTransform;
					st.volume = _volume;
					_soundChannel.soundTransform = st;
				}
			}
		}
		
		[Bindable]
		public function get volume():Number
		{
			if(_mute)
			{
				return 0;
			}
			else 
			{
				return _volume;
			}
		}
		
		public function set mute(value:Boolean):void
		{
			if (_mute != value)
			{
				toggleMute();
			}
		}
		
		[Bindable]
		public function get mute():Boolean
		{
			return _mute;
		}
		
		public function toggleMute():void
		{
			_mute = !_mute;
			var st:SoundTransform;
			if(_mute)
			{
				st = _soundChannel.soundTransform;
				st.volume = 0;
				_soundChannel.soundTransform = st;
			}
			else
			{
				st = _soundChannel.soundTransform;
				st.volume = _volume;
				_soundChannel.soundTransform = st;
			}
		}
        
        [Bindable]
		/**
		 * Sound file loading progress value (%)
		 * @return percentage of file loading
		 * 
		 */
        public function get loadingProgressValue():uint
        {
        	return _loadingProgressValue;
        }
		/**
		 * Setter file loading progress value  
		 * @param value
		 * 
		 */        
        public function set loadingProgressValue(value:uint):void
        {
        	_loadingProgressValue = value;
        	dispatchEvent(new Event(TRACK_LOADING_PROGRESS_CHANGED));
        }
        
		[Bindable]
		/**
		 * Playing position.
		 * @return 
		 * 
		 */
        public function get trackDisplayPosition():Number
        {
        	return _trackDisplayPosition;
        }
        
        public function set trackDisplayPosition(value:Number):void
        {
        	_trackDisplayPosition= value;
        	dispatchEvent(new Event(TRACK_POSITION_CHANGED));
        }
		
        
		public static function getInstance():SoundManager
		{
			if (SoundManager._instance == null)
			{
				SoundManager._instance = new SoundManager(new SoundManagerInitClass());
			}
			return SoundManager._instance;
		}
		
        public function SoundManager(init:SoundManagerInitClass)
        {
			_playState = new PlayState(this);
			_stopState = new StopState(this);
			_pauseState = new PauseState(this);
			_currentState = _stopState;
        }
		
        
        public function loadAndPlayTrack(track:ITrack):void 
        {
        	stopPlay();
			if (track.location != null && track.location != "")
			{
				loadTrack(track.location);
				startPlay();
			}
        }
        
        public function loadTrack(trackLocation:String):void
        {
            // Check if location of track is not empty
			var soundURL:URLRequest = new URLRequest(trackLocation);
            _sound = new Sound();
			addSoundEventListeners(_sound);
            _sound.load(soundURL);
        }
        
        public function startPlay():void
        {
			_soundChannel = _currentState.startPlay(_sound, _soundChannel, _trackPosition);
			if(!_soundChannel.hasEventListener(Event.SOUND_COMPLETE))
			{
				_soundChannel.addEventListener(Event.SOUND_COMPLETE, soundCompleteEventHandler);
			}
			startTimer();
        }
		
		public function stopPlay():void
		{
			_currentState.stopPlay(_soundChannel);
		}
		
		public function pause():void
		{
			_trackPosition =  _currentState.pause(_soundChannel);
		}
        
		public function setState(state:ISoundState):void
		{
			_currentState = state;
		}
		
		public function setTrackPosition(newTrackPosition:Number):void
		{
			pause();
			_trackPosition = (_sound.length * newTrackPosition)/ 100;
			startPlay();
		}
        
        private function addSoundEventListeners(sound:Sound):void
        {
            _sound.addEventListener(ProgressEvent.PROGRESS, soundLoadingProgressEventHandler);
			_sound.addEventListener(Event.COMPLETE, soundLoadCompleteHandler);
			_sound.addEventListener(Event.ID3, id3RecievedHandler);
        }
		
        /*
           Event handlers
        */
		
		private function soundCompleteEventHandler(event:Event):void
		{
			dispatchEvent(new Event(TRACK_PLAYING_COMPLETE));
		}
		
        private function soundLoadingProgressEventHandler(progressEvent:ProgressEvent):void
        {
            //loadingProgressValue = (progressEvent.bytesLoaded / progressEvent.bytesTotal) * 100;
        }
		
		private function soundLoadCompleteHandler(event:Event):void
		{
			trackTime = trackTimeToString(_sound.length);
		}
		
		/**
		 * Handles recieving ID3 information from sound  
		 * @param event
		 * 
		 */
		private function id3RecievedHandler(event:Event):void
		{
			id3Info = new ID3InfoWrapper(Sound(event.target).id3);
		}
        
        
        private function positionTimerEventHandler(event:TimerEvent):void
        {
           trackDisplayPosition = 100 * (_soundChannel.position / _sound.length);
		   currentTime = trackTimeToString(_soundChannel.position);
        }
        
        
        private function startTimer():void
        {
            _trackPositionTimer = new Timer(50);
            _trackPositionTimer.addEventListener(TimerEvent.TIMER, positionTimerEventHandler);
            _trackPositionTimer.start();
        }
		
		private function trackTimeToString(tracktime:Number):String
		{
			var timeString:String;
			var totalSecond:Number = tracktime / 1000;
			var minutes:Number = (totalSecond - (totalSecond%60))/60;
			var seconds:Number = Math.round(totalSecond%60);
			timeString = minutes.toString() + TIME_DELIMITER + ("0" + seconds.toString()).substr(-2);
			return timeString;
		}
    }
}

internal class SoundManagerInitClass
{
	public function SoundManagerInitClass()
	{
		
	}
}